<html>
<head>
  <title>Evernote Export</title>
  <basefont face="微软雅黑" size="2" />
  <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
  <meta name="exporter-version" content="Evernote Windows/307934 (zh-CN, DDL); Windows/6.1.1 (Win64);"/>
  <style>
    body, td {
      font-family: 微软雅黑;
      font-size: 10pt;
    }
  </style>
</head>
<body>
<a name="414"/>

<div>
<span><div><div><div><ul><li><div><span style="font-size: 18pt;">目标：一个容错（fault-tolerant）的key/value存储系统</span></div></li></ul><div style="margin-left: 40px;"><span style="font-size: 10pt;">Lab2：实现Raft，一种复制状态机协议。</span></div><div style="margin-left: 40px;"><span style="font-size: 10pt;">Lab3：在Raft之上构建一个key/value服务</span></div><div style="margin-left: 40px;"><span style="font-size: 10pt;">Lab4：在多个复制状态机（replicated state machines）上分片（shard）服务以获得更高的性能</span></div><ul><li><div><span style="font-size: 18pt;">容错（fault tolerance）</span></div></li></ul><div style="margin-left: 40px;"><span style="font-size: 10pt;">复制服务（a replicated service）通过在多个副本服务器（replica serves）上存储其状态(即数据)的完整拷贝（complete copies）来实现容错</span></div><div style="margin-left: 40px;"><span style="font-size: 10pt; color: rgb(255, 0, 0);">挑战在于故障（failures）可能导致副本（replicas）保存不同的数据拷贝（differing copies of data）</span></div><ul><li><div><span style="font-size: 18pt;">Raft</span></div></li></ul><div style="margin-left: 40px;"><span style="font-size: 10pt;">Raft管理服务的状态副本，特别是它可以帮助服务找出失败后的正确状态。</span></div><ul><ul><li><div><span style="font-size: 14pt;">复制状态机（a replicated state machine）</span></div></li></ul></ul><div style="margin-left: 80px;"><span style="font-size: 10pt;">Raft实现了一个复制状态机。</span></div><div style="margin-left: 80px;"><span style="font-size: 10pt;">(1) 它将客户端请求组织为一个称为日志（log）的序列，并确保所有的副本（replicas）都同意日志的内容</span></div><div style="margin-left: 80px;"><span style="font-size: 10pt;">(2) 每个副本按日志中出现的顺序执行客户端请求，将这些请求应用到副本的服务状态的本地拷贝</span></div><ul><ul><li><div><span style="font-size: 14pt;">正确性：</span></div></li></ul></ul><div style="margin-left: 80px;"><span style="font-size: 10pt;">由于所有的活跃副本（live replicas）都看到相同的日志内容，因此它们都以相同的顺序执行相同的请求，因此继续持有相同服务状态</span></div><ul><ul><li><div><span style="font-size: 14pt;">容错：</span></div></li></ul></ul><div style="margin-left: 80px;"><span style="font-size: 10pt;">(1) 如果服务器出现故障但稍后恢复，Raft负责使其日志更新</span></div><div style="margin-left: 80px;"><span style="font-size: 10pt;">(2) 只要至少大多数服务器处于活跃状态并且可以互相通信，Raft将继续运行</span></div><div style="margin-left: 80px;"><span style="font-size: 10pt;">(3) 如果没有这样的多数，Raft将不会取得任何进展，但只要大多数能够再次通信，它就会从中断的地方继续运行</span></div><ul><ul><li><div><span style="font-size: 14pt;">日志条目（log entries）</span></div></li></ul></ul><div style="margin-left: 80px;"><span style="font-size: 10pt;">(1) 实现的Raft接口将支持无限长的编号命令序列（an indefinite sequence of numbered commands），也称为日志条目（log entries）</span></div><div style="margin-left: 80px;"><span style="font-size: 10pt;">(2) 条目（entries）用索引号（index numbers）编号</span></div><div style="margin-left: 80px;"><span style="font-size: 10pt;">(3) 最终将提交具有给定索引的日志条目</span></div><div style="margin-left: 80px;"><span style="font-size: 10pt;">(4) 此时，实现的Raft应该将日志条目发送到更大的服务以便执行</span></div><ul><li><div><span style="font-size: 18pt;">任务</span></div></li></ul><div style="margin-left: 40px;"><span style="font-size: 10pt;">实现extended-raft论文中描述的大多数Raft设计，包括保存持久化状态（persitent state）并在节点故障后读取它并重新启动。</span></div><div style="margin-left: 40px;"><span style="font-size: 10pt;">不需要实现集群成员关系（cluster menbership changes）（Section 6）和日志压缩/快照（log compaction/ snapshotting）（Section 7）。</span></div><div style="margin-left: 40px;"><span style="font-size: 10pt;">实现分为三个部分：</span></div><div style="margin-left: 40px;"><span style="font-size: 10pt;">Part 2A：领导选举。实现领导者选举和心跳（leader election and heartbeats）（借助不包含日志条目的追加条目RPCs（AppendEntries RPCs））</span></div><div style="margin-left: 40px;"><span style="font-size: 10pt;">Part 2B：日志复制。</span></div><div style="margin-left: 40px;"><span style="font-size: 10pt;">Part 2C：保存持久化状态</span></div><div style="margin-left: 40px;"></div></div><ul><li><div><span style="font-size: 18pt;">代码</span></div></li></ul><div style="margin-left: 40px;"><span style="font-size: 10pt; font-weight: bold;">通过向raft/raft.go添加代码来实现Raft</span><span style="font-size: 10pt;">。在该文件中你将找到一些框架代码（a bit of skeleton code），以及如何发送和接收RPCs的例子。</span></div><div style="margin-left: 40px;"><span style="font-size: 10pt;">你的实现必须支持以下接口，测试代码和（最终）你的key/value服务器将使用这些接口。你能从raft.go中的注释里找到更多详细信息。</span></div><div style="margin-left: 40px;"><span style="background-color: rgb(255, 250, 165); font-family: Consolas;-evernote-highlight:true;">// create a new Raft server instance:</span></div><div style="margin-left: 40px;"><span style="background-color: rgb(255, 250, 165); font-family: Consolas;-evernote-highlight:true;">rf := Make(peers, me, persister, applyCh)</span></div><div style="margin-left: 40px;"><font face="Consolas" style="background-color: rgb(255, 250, 165);-evernote-highlight:true;"><br/></font></div><div style="margin-left: 40px;"><span style="background-color: rgb(255, 250, 165); font-family: Consolas;-evernote-highlight:true;">// start agreement on a new log entry:</span></div><div style="margin-left: 40px;"><span style="background-color: rgb(255, 250, 165); font-family: Consolas;-evernote-highlight:true;">rf.Start(command interface{}) (index, term, isleader)</span></div><div style="margin-left: 40px;"><font face="Consolas" style="background-color: rgb(255, 250, 165);-evernote-highlight:true;"><br/></font></div><div style="margin-left: 40px;"><span style="background-color: rgb(255, 250, 165); font-family: Consolas;-evernote-highlight:true;">// ask a Raft for its current term, and whether it thinks it is leader</span></div><div style="margin-left: 40px;"><span style="background-color: rgb(255, 250, 165); font-family: Consolas;-evernote-highlight:true;">rf.GetState() (term, isLeader)</span></div><div style="margin-left: 40px;"><font face="Consolas" style="background-color: rgb(255, 250, 165);-evernote-highlight:true;"><br/></font></div><div style="margin-left: 40px;"><span style="background-color: rgb(255, 250, 165); font-family: Consolas;-evernote-highlight:true;">// each time a new entry is committed to the log, each Raft peer</span></div><div style="margin-left: 40px;"><span style="background-color: rgb(255, 250, 165); font-family: Consolas;-evernote-highlight:true;">// should send an ApplyMsg to the service (or tester).</span></div><div style="margin-left: 40px;"><span style="background-color: rgb(255, 250, 165); font-family: Consolas;-evernote-highlight:true;">type ApplyMsg</span></div><div style="margin-left: 40px;">一个服务调用<span style="background-color: rgb(255, 250, 165);-evernote-highlight:true;"><span style="background-color: rgb(255, 250, 165); font-family: Consolas;-evernote-highlight:true;">Make(peers, me, ...)</span></span>来创建一个Raft对等点（a Raft peer）。“<span style="background-color: rgb(255, 250, 165);-evernote-highlight:true;"><span style="background-color: rgb(255, 250, 165); font-family: Consolas;-evernote-highlight:true;">peers</span></span>”参数是Raft对等点（包括这个）的网络标识符数组，用于labrpc RPC。“<span style="background-color: rgb(255, 250, 165);-evernote-highlight:true;"><span style="background-color: rgb(255, 250, 165); font-family: Consolas;-evernote-highlight:true;">me</span></span>”参数是对等点数组中该对等点的索引。</div><div style="margin-left: 40px;">Start(command)要求Raft启动处理以便将命令追加到复制日志。Start()应该立即返回，而不必等待日志追加完成。</div><div style="margin-left: 40px;">该服务期望你的实现为每一个新提交的日志条目（each newly committed log entry）发送一个<span style="background-color: rgb(255, 250, 165);-evernote-highlight:true;"><span style="background-color: rgb(255, 250, 165); font-family: Consolas;-evernote-highlight:true;">ApplyMsg</span></span>到<span style="background-color: rgb(255, 250, 165);-evernote-highlight:true;"><span style="background-color: rgb(255, 250, 165); font-family: Consolas;-evernote-highlight:true;">Make()</span></span>函数的“<span style="background-color: rgb(255, 250, 165);-evernote-highlight:true;"><span style="background-color: rgb(255, 250, 165); font-family: Consolas;-evernote-highlight:true;">applyCh</span></span>”参数。</div><div style="margin-left: 40px;">你的Raft对等点应该使用我们提供给你的Go语言包labrpc来交换RPCs。它以Go语言的rpc库为模型（is modeled after Go's rpc library），但是内部使用Go channels而不是sockets。raft.go包含一些发送RPC（<span style="background-color: rgb(255, 250, 165);-evernote-highlight:true;"><span style="background-color: rgb(255, 250, 165); font-family: Consolas;-evernote-highlight:true;">sendRequestVote()</span></span>）和处理传入的RPC（<span style="background-color: rgb(255, 250, 165);-evernote-highlight:true;"><span style="background-color: rgb(255, 250, 165); font-family: Consolas;-evernote-highlight:true;">RequestVote()</span></span>）的示例代码。你必须使用labrpc而不是Go的RPC包的原因是，测试代码告诉labrpc延迟RPCs、重新排序它们、删除它们来模拟具有挑战性的网络条件，在这些条件下你的代码应该正确工作（work correctly）。</div><div style="margin-left: 40px;"><span style="font-weight: bold;">不要依赖对labrpc的修改，因为我们将使用分发的labrpc来测试你的代码</span>。</div><div style="margin-left: 40px;">你的第一个实现可能不够清晰（clean）以使你很容易地推断出它的正确性。<span style="font-weight: bold;">给自己足够的时间重写你的实现以至于你可以很容易推断出它的正确性</span>。后序的实验建立在这个实验的基础上，所以在实现上做好工作非常重要。</div><ul><li><div><span style="font-size: 18pt;">Part 2A</span></div></li></ul><div style="margin-left: 40px;">提示：</div><ul><ul><li><div><span style="font-weight: bold;">往raft.go中的Raft结构里增加任何你需要的状态。你还需要定义一个结构来保存有关每个日志条目的信息。</span>你的代码应该尽可能遵从论文中的图2。</div></li><li><div><span style="font-weight: bold;">填充</span><span style="background-color: rgb(255, 250, 165);-evernote-highlight:true;"><span style="background-color: rgb(255, 250, 165); font-family: Consolas; font-weight: bold;-evernote-highlight:true;">RequestVoteArgs</span></span><span style="font-weight: bold;">和</span><span style="background-color: rgb(255, 250, 165);-evernote-highlight:true;"><span style="background-color: rgb(255, 250, 165); font-family: Consolas; font-weight: bold;-evernote-highlight:true;">RequestVoteReply</span></span><span style="font-weight: bold;">结构</span>。<span style="font-weight: bold;">修改</span><span style="background-color: rgb(255, 250, 165);-evernote-highlight:true;"><span style="background-color: rgb(255, 250, 165); font-family: Consolas; font-weight: bold;-evernote-highlight:true;">Make()</span></span><span style="font-weight: bold;">函数来创建一个后台goroutine，当有一段时间内没有收到来自其他对等点的消息时，通过发送</span><span style="background-color: rgb(255, 250, 165);-evernote-highlight:true;"><span style="background-color: rgb(255, 250, 165); font-family: Consolas; font-weight: bold;-evernote-highlight:true;">RequestVote</span></span> <span style="font-weight: bold;">RPCs来周期性地启动（kick off）领导者选举</span>。通过这种方式（this way）一个对等点将了解谁是领导者，如果已经有领导者，或者成为一个领导者本身。实<span style="font-weight: bold;">现</span><span style="background-color: rgb(255, 250, 165);-evernote-highlight:true;"><span style="background-color: rgb(255, 250, 165); font-family: Consolas; font-weight: bold;-evernote-highlight:true;">RequestVote()</span></span> <span style="font-weight: bold;">RPC处理程序，以便服务器可以为其他服务器投票</span>。</div></li><li><div><span style="font-weight: bold;">要实现心跳（heartbeats），请定义一个</span><span style="background-color: rgb(255, 250, 165);-evernote-highlight:true;"><span style="background-color: rgb(255, 250, 165); font-family: Consolas; font-weight: bold;-evernote-highlight:true;">AppendEntries</span></span> <span style="font-weight: bold;">RPC结构（尽管你可能还不需要所有参数），并让leader定期发送它们</span>。<span style="font-weight: bold;">编写一个</span><span style="background-color: rgb(255, 250, 165);-evernote-highlight:true;"><span style="background-color: rgb(255, 250, 165); font-family: Consolas; font-weight: bold;-evernote-highlight:true;">AppendEntries</span></span> <span style="font-weight: bold;">RPC的处理程序方法，该方法重置选举超时</span>，以便其他服务器在一个服务器已经被选中时不会作为leader向前迈进。</div></li><li><div><span style="font-weight: bold;">确保不同对等点的选举超时不总是同时发生</span>，否则所有的对等点都只会给自己投票，没有谁会成为leader。</div></li><li><div><span style="font-weight: bold;">测试代码要求leader每秒发送心跳RPCs不超过10次</span>。</div></li><li><div><span style="font-weight: bold;">测试代码要求你的Raft在旧的领导者失败后5秒内要选出一个新的领导者</span>（如果大多数的对等点仍能能够通信的话）。<span style="font-weight: bold;">然而，请记住，在瓜分投票（a split vote）的情况下，领导选举可能需要多轮</span>（如果信息包丢失或者候选者不幸选择了相同的随机回退时间（random backoff times），可能会发生这种情况）。<span style="font-weight: bold;">你必须选择足够短的选举时间（并且也是心跳间隔（heartbeat intervals））</span>，使得即使需要多轮，一次选举也非常可能在不到5秒的时间内完成。</div></li><li><div>论文5.2节提到选举超时在150到300ms的范围内。<span style="font-weight: bold;">只有当领导者比每150ms发送一次以上的心跳时，这个范围才有意义</span>。因为测试代码将你的心跳限制在每秒10次，所以<span style="font-weight: bold;">你必须使用比论文中150到300ms更长的选举超时，但是不能太长</span>，因为这样你可能无法在5秒内选出一个领导者。</div></li><li><div><span style="font-weight: bold;">你可能会发现Go的</span><span style="background-color: rgb(255, 250, 165);-evernote-highlight:true;"><span style="background-color: rgb(255, 250, 165); font-family: Consolas; font-weight: bold;-evernote-highlight:true;">rand</span></span><span style="font-weight: bold;">很有用</span>。</div></li><li><div><span style="font-weight: bold;">你需要编写定期或者延时时间后执行操作的代码。最简单的方法是创建一个循环调用</span><span style="background-color: rgb(255, 250, 165);-evernote-highlight:true;"><span style="background-color: rgb(255, 250, 165); font-family: Consolas; font-weight: bold;-evernote-highlight:true;">ran</span></span><span style="font-weight: bold;">的goroutine</span>。困难的方法是使用Go的<span style="background-color: rgb(255, 250, 165);-evernote-highlight:true;"><span style="background-color: rgb(255, 250, 165); font-family: Consolas;-evernote-highlight:true;">time.Timer</span></span>或<span style="background-color: rgb(255, 250, 165);-evernote-highlight:true;"><span style="background-color: rgb(255, 250, 165); font-family: Consolas;-evernote-highlight:true;">time.Ticker</span></span>，这些都很难正确使用。</div></li><li><div>如果你对锁（locking）感到疑惑，你可能会发现<span style="text-decoration: underline;">这个建议</span>很有用。</div></li><li><div>如果你的代码无法通过测试，请再次阅读论文的图2；<span style="font-weight: bold;">领导者选举的完整逻辑分布在（is spread over）图中的多个部分</span>。</div></li><li><div><span style="font-weight: bold;">调试代码的一个好方法是，当对等点发送或接收消息时插入打印语句，并且使用</span><span style="background-color: rgb(255, 250, 165);-evernote-highlight:true;"><span style="background-color: rgb(255, 250, 165); font-family: Consolas; font-weight: bold;-evernote-highlight:true;">go test -run 2A &gt; out</span></span><span style="font-weight: bold;">收集输出到一个文件</span>。然后，通过研究<span style="background-color: rgb(255, 250, 165);-evernote-highlight:true;"><span style="background-color: rgb(255, 250, 165); font-family: Consolas;-evernote-highlight:true;">out</span></span>文件中的消息跟踪，你可以确定你的实现在哪里偏离了协议的期望。你可能会发现util.go中的<span style="background-color: rgb(255, 250, 165);-evernote-highlight:true;"><span style="background-color: rgb(255, 250, 165); font-family: Consolas;-evernote-highlight:true;">DPrintf</span></span>是有用的，在调试不同的问题时打开或关闭打印。</div></li><li><div><span style="font-weight: bold;">Go RPC只发送名称以大写字母开头（start with captial letters）的struct字段</span>。子结构也必须有大写的字段名（例如，数组中的日志记录字段）。labgob包会警告你这一点，不要忽略这些警告。</div></li><li><div>你应该使用<span style="background-color: rgb(255, 250, 165);-evernote-highlight:true;"><span style="background-color: rgb(255, 250, 165); font-family: Consolas;-evernote-highlight:true;">go test -race</span></span>检查代码，并修复它报告的任何竞争（race）。</div></li></ul><li><div><span style="font-size: 18pt;">Part 2B</span></div></li></ul><div style="margin-left: 40px;">实现领导者和跟随者代码来追加新的日志条目。这将涉及到实现<span style="background-color: rgb(255, 250, 165);-evernote-highlight:true;"><span style="background-color: rgb(255, 250, 165); font-family: Consolas;-evernote-highlight:true;">Start()</span></span>，完成AppendEntires RPC结构，发送它们，充实（fleshing out）AppendEntry RPC处理程序（handler），以及推进（advancing）领导者的<span style="background-color: rgb(255, 250, 165);-evernote-highlight:true;"><span style="background-color: rgb(255, 250, 165); font-family: Consolas;-evernote-highlight:true;">commitIndex</span></span>。</div><div style="margin-left: 40px;">你的第一个目标应该是通过<span style="background-color: rgb(255, 250, 165);-evernote-highlight:true;"><span style="background-color: rgb(255, 250, 165); font-family: Consolas;-evernote-highlight:true;">TestBasicAgree2B()</span></span>测试（在test_test.go中）。一旦你成功了，你应该通过所有的2B测试（go test run -2B）。</div><div style="margin-left: 40px;">提示（Hint）：</div><ul><ul><li><div>你将需要实现选举限制（restriction）（论文5.4.1节）</div></li><li><div>不通过Lab 2B早期测试的一个方式是举行（hold）不需要的（un-needed）选举，也就是说，即使现任领导者还存活，而且可以与所有peers交谈，选举也要举行。这可以防止在测试者相信可能达成一致的场景下达成一致。选举计时器（election timer）管理中的bug，或者赢得选举后没有立即（immediately）发出（send out）心跳，可能会造成不必要的选举。</div></li><li><div>你可能需要等编写等待某些事件出现（occur）的代码。不要编写在没有暂停的情况下连续（continuously）执行的循环，因为这会使你的实现慢到测试失败。你可以使用Go的channels，或者Go的条件变量，或者（如果其他方法都失败了）在每个循环迭代中插入一条time.Sleep(10 * time.Millisecond)来有效地等待。</div></li><li><div>根据从组织并发代码中学到的经验（in light of lessons learned about structuring concurrent code），给自己时间重写实现。在以后的实验中，你将感谢自己拥有尽可能清晰（clear）和干净（clean）的Raft代码。如果你有什么想法，你可以重新访问（re-visit）下我们的<span style="text-decoration: underline;">结构</span>，<span style="text-decoration: underline;">锁</span>和<span style="text-decoration: underline;">指南</span>页面。</div></li></ul><div>“ok raft 38.029s”意味着Go测量2B所有的测试花费的时间是实际（挂钟（wall-clock））时间的38.029秒。“user 0m1.460s”意味着代码消耗了1.460秒的CPU时间，即实际（actually）执行指令（而不是等待或睡眠）的时间。<span style="font-weight: bold;">如果你的解决方案用于2B所有测试的实际时间远远超过（much more than）1分钟，或者CPU时间远远超过（much more than）5秒，那么你以后可能会遇到麻烦。</span>查找花费在睡眠或等待RPC超时的时间，运行时没有睡眠或没有等待条件或channel消息的循环，或发送的大量RPC。</div><li><div><font style="font-size: 18pt;">Part 2C</font></div></li></ul><div style="margin-left: 40px;">如果一个基于Raft（Raft-based）的服务器重新启动，它应该从停止的地方（where it left off）恢复（resume）服务。这要求Raft保持在重启后仍然存在的（that survives a reboot）持久状态（persistent state）。论文图2提到了哪些状态（which state）应该是持久的，并且raft.go包含了如何保存（save）和恢复（restore）持久状态的示例。</div></div><div style="margin-left: 40px;">“真正的（real）”实现可以做到这一点，通过在每次发生变化时将Raft的持久状态写入磁盘，并在重启后重新启动时从磁盘读取最新（latest）保存的状态。你的实现不使用磁盘；相反，它将从Persister对象（见persister.go）保存和恢复持久状态。<span style="background-color: rgb(255, 250, 165);-evernote-highlight:true;"><font style="font-family: Consolas;">Raft.Make()</font></span>的调用者提供了初始时持有（initially holds）Raft最近（most recently）持久状态（如何有的话（if any））的Persister。Raft应该从那个Persister初始化它的状态，并且应该在每次状态变化时使用它来保存其持久状态。使用Persister的<span style="background-color: rgb(255, 250, 165);-evernote-highlight:true;"><font style="font-family: Consolas;">ReadRaftState()</font></span>和<span style="background-color: rgb(255, 250, 165);-evernote-highlight:true;"><font style="font-family: Consolas;">SaveRaftState()</font></span>方法。</div><div style="margin-left: 40px;">通过添加保存和恢复持久状态的代码，来完成raft.go中<span style="background-color: rgb(255, 250, 165);-evernote-highlight:true;"><font style="font-family: Consolas;">persist()</font></span>和<span style="background-color: rgb(255, 250, 165);-evernote-highlight:true;"><font style="font-family: Consolas;">readPersist()</font></span>函数。你需要将状态编码（encode）（或序列化（serialize））为字节数组（an array of bytes）以便将它传递给Persister。使用我们提供的labgob编码器来完成此操作；请参阅persist()和readPersist()中的注释。labgob来源于（is derived from）Go的gob编码器；唯一的区别是，如果你尝试用小写字段名称（lower-case field names）编码结构，labgob将打印错误消息。</div><div style="margin-left: 40px;">现在你需要确定（determine）在Raft协议中的哪些点（what points in the Raft protocol）你的服务器被要求持久化其状态，并在这些地方插入对persist()的调用。在Raft.Make()中已经有对readPersister()的调用。完成这些之后，你应该通过剩余的测试。你可能想先尝试通过“basic persistence”测试（go test -run 'TestPersist12C'），然后处理（tackle）其余的测试（go test -run 2C）。</div><ul><ul><li><div><font style="font-size: 14pt;">注意</font></div></li></ul><div style="margin-left: 40px;"><font style="font-size: 10pt;">为了避免运行内存耗尽（out of memory），Raft必须定期（periodically）丢弃（discard）旧的日志条目，但是直到下个实验之前（until the next lab）你不必（do not have to）担心这个问题。</font></div><ul><li><div><font style="font-size: 14pt;">提示</font></div></li><ul><li><div><font style="font-size: 10pt;">2C的许多测试涉及到服务器故障（failing）和网络丢失RPC请求（requests）或回复（replies）</font></div></li><li><div>为了在最后（towards the end）通过一些具有挑战性的（challenging）测试，例如（such as）那些标记为“不可靠的（unreliable）”的测试，你将需要实现优化，以允许跟随者一次通过多个条目（by more than one entry at a time）回退（back up）领导者的nextIndex。请参阅the extended Raft论文的从第7页底部和第8页顶部（用灰色线标记）开始的描述。论文在细节上含糊不清（vegue）；你将需要填补这些空白（the gaps），可能需要借助6.824的Raft课件（lectures）的帮助。</div></li><li><div>整套Lab2测试（a full set of Lab 2 tests）（2A+2B+2C）合理的时间消耗（a reasonable amount of time to consume）是4分钟的真实时间和1分钟的CPU时间。</div></li></ul></ul></ul><div><br/></div></div></span>
</div></body></html> 